﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lexer.SyntaxTree;
namespace Lexer
{
    class Script
    {
        /// <summary>
        /// A list of subscripts
        /// </summary>
        private Dictionary<String, SubscriptSyntaxNode> subscripts;
        /// <summary>
        /// Creates a script from file.
        /// </summary>
        /// <param name="filename"></param>
        public Script(String filename)
        {
            this.subscripts = new Dictionary<string,SubscriptSyntaxNode>();
            // get the lexer
            Lexer newLexer = new Lexer(filename);
            SubscriptSyntaxNode currentSubscript = null;
            do
            {
                // looks for subscript keywords, and thats it really it for the top level
                switch (newLexer.CurrentToken.Type)
                {
                    case TokenType.KEYWORD:
                        switch (newLexer.CurrentToken.StringValue)
                        {
                            case "subscript":
                                currentSubscript = new SubscriptSyntaxNode();
                                // consumes the subscript token
                                newLexer.NextToken();
                                // reads in the subscript name
                                if (newLexer.CurrentToken.Type != TokenType.IDENTIFIER)
                                    throw new Exception("Identifier expected.");
                                currentSubscript.Name = newLexer.CurrentToken.StringValue;
                                // consumes the identifier token
                                newLexer.NextToken();
                                // checks to see if its a multi line, or single line subscript.
                                switch (newLexer.CurrentToken.Type)
                                {
                                    case TokenType.OPEN_BRACE:
                                        currentSubscript.NextNode = Script.ProcessCodeBlock(newLexer);
                                        break;
                                    case TokenType.KEYWORD:
                                        currentSubscript.NextNode = Script.ProcessCodeLine(newLexer);
                                        break;
                                    default:
                                        throw new Exception("'{', keyword expected.");
                                }
                                this.subscripts[currentSubscript.Name] = currentSubscript;
                                break;
                            default:
                                throw new Exception("Keyword 'subscript' expected.");
                        }
                        break;
                }
            } while (newLexer.CurrentToken.Type != TokenType.END_OF_FILE);
            newLexer.Close();
        }
        public SubscriptSyntaxNode this[String key]
        {
            get { return this.subscripts[key]; }
        }
        /// <summary>
        /// Processes a single line of code upto a semicolon.  Starts at the keyword.  Eats the semicolon.
        /// </summary>
        /// <param name="lex">The lexer to read from.</param>
        /// <returns>The SyntaxNode representing the line.</returns>
        private static SyntaxNode ProcessCodeLine(Lexer lex)
        {
            if (lex.CurrentToken.Type != TokenType.KEYWORD)
                throw new Exception("Keyword expected.");
            switch (lex.CurrentToken.StringValue)
            {
                case "set":
                    //consume the set token
                    lex.NextToken();
                    if (lex.CurrentToken.Type != TokenType.IDENTIFIER)
                        throw new Exception("Identifier expected.");
                    SetSyntaxNode retSet = new SetSyntaxNode();
                    retSet.VariableName = lex.CurrentToken.StringValue;
                    // consume the ident
                    lex.NextToken();
                    retSet.Expr = Script.ProcessExpression(lex);
                    if (lex.CurrentToken.Type != TokenType.END_LINE)
                        throw new Exception("';' expected.");
                    // consume the semicolon
                    lex.NextToken();
                    return retSet;
                case "event":
                    // consume the event keyword token
                    lex.NextToken();
                    if (lex.CurrentToken.Type != TokenType.IDENTIFIER)
                        throw new Exception("Identifier expected.");
                    EventSyntaxNode retEvent = new EventSyntaxNode();
                    retEvent.Name = lex.CurrentToken.StringValue;
                    // consume the name identifier token
                    lex.NextToken();
                    if (lex.CurrentToken.Type != TokenType.OPEN_PAREN)
                        throw new Exception("'(' expected.");
                    // consume the open paren token
                    lex.NextToken();
                    BinaryOperationSyntaxNode temp;
                    while(lex.CurrentToken.Type != TokenType.CLOSE_PAREN)
                    {
                        temp = Script.ProcessExpression(lex);
                        if (!(temp.LHS is IdentifierSyntaxNode))
                            throw new Exception("Identifier expected.");
                        if (!(temp.RHS is StringLiteralSyntaxNode || temp.RHS is PropertySyntaxNode || temp.RHS is NumericLiteralSyntaxNode || temp.RHS is IdentifierSyntaxNode))
                            throw new Exception("Number, string, identifier, or property expected.");
                        if (temp.OpCode != "=")
                            throw new Exception("'=' expected.");
                        retEvent.AddArgument(temp);
                    }
                    // consume the close paren token
                    lex.NextToken();
                    if (lex.CurrentToken.Type != TokenType.END_LINE)
                        throw new Exception("';' expected.");
                    // consume the semicolon token
                    lex.NextToken();
                    return retEvent;
                default:
                    throw new Exception("Invalid keyword encountered.");
                    
            }
        }
        /// <summary>
        /// Processes a binary operation.  Starts at the first argument.  Eats the last argument.
        /// </summary>
        /// <param name="lex">The lexer to process.</param>
        /// <returns>An expression.</returns>
        private static BinaryOperationSyntaxNode ProcessExpression(Lexer lex)
        {
            BinaryOperationSyntaxNode op = new BinaryOperationSyntaxNode();
            op.LHS = Script.ProcessOperand(lex);
            if (lex.CurrentToken.Type != TokenType.OPCODE)
                throw new Exception("Operator expected.");
            op.OpCode = lex.CurrentToken.StringValue;
            // consume the operator token
            lex.NextToken();
            op.RHS = Script.ProcessOperand(lex);
            return op;
        }
        /// <summary>
        /// Processes an if line.  Starts at the if keyword.  Eats the last brace or semicolon.
        /// </summary>
        /// <param name="lex">The lexer to process.</param>
        /// <returns>The if statement and its two subtrees.</returns>
        private static IfSyntaxNode ProcessIfLine(Lexer lex)
        {
            if (lex.CurrentToken.Type != TokenType.KEYWORD || lex.CurrentToken.StringValue != "if")
                throw new Exception("Keyword expected.");
            // consume if keyword token
            lex.NextToken();
            // validate open paren
            if (lex.CurrentToken.Type != TokenType.OPEN_PAREN)
                throw new Exception("'(' expected.");
            // consume open paren token
            lex.NextToken();
            BinaryOperationSyntaxNode op = Script.ProcessExpression(lex);
            
            if (lex.CurrentToken.Type != TokenType.CLOSE_PAREN)
                throw new Exception("')' expected");
            IfSyntaxNode ret = new IfSyntaxNode();
            ret.Condition = op;
            // consume close paren token
            lex.NextToken();
            switch (lex.CurrentToken.Type)
            {
                case TokenType.OPEN_BRACE:
                    ret.IfTrue = Script.ProcessCodeBlock(lex);
                    break;
                case TokenType.KEYWORD:
                    ret.IfTrue = Script.ProcessCodeLine(lex);
                    break;
                default:
                    throw new Exception("'{', or keyword expected.");
            }
            if (lex.CurrentToken.Type == TokenType.KEYWORD && lex.CurrentToken.StringValue == "else")
            {
                // consume the else keyword token
                lex.NextToken();
                switch (lex.CurrentToken.Type)
                {
                    case TokenType.OPEN_BRACE:
                        ret.IfFalse = Script.ProcessCodeBlock(lex);
                        
                        break;
                    case TokenType.KEYWORD:
                        ret.IfFalse = Script.ProcessCodeLine(lex);
                        break;
                    default:
                        throw new Exception("'{', or keyword expected.");
                }
            }
            else
            {
                ret.IfFalse = null;
            }
            return ret;
        }
        /// <summary>
        /// Looks at the current token and process it into a SyntaxNode operand.  Starts and eats the operand.
        /// </summary>
        /// <param name="lex">Lexer to read from.</param>
        /// <returns>Operand processed.</returns>
        private static ArgumentSyntaxNode ProcessOperand(Lexer lex)
        {
            ArgumentSyntaxNode ret;
            switch(lex.CurrentToken.Type)
            {
                case TokenType.PROPERTY:
                    PropertySyntaxNode prop = new PropertySyntaxNode();
                    prop.PropertyName = lex.CurrentToken.StringValue;
                    ret = prop;
                    break;
                case TokenType.NUMBER_LITERAL:
                    NumericLiteralSyntaxNode num = new NumericLiteralSyntaxNode();
                    num.Value = lex.CurrentToken.IntValue;
                    ret = num;
                    break;
                case TokenType.STRING_LITERAL:
                    StringLiteralSyntaxNode str = new StringLiteralSyntaxNode();
                    str.Value = lex.CurrentToken.StringValue;
                    ret = str;
                    break;
                case TokenType.IDENTIFIER:
                    IdentifierSyntaxNode id = new IdentifierSyntaxNode();
                    id.Value = lex.CurrentToken.StringValue;
                    ret = id;
                    break;
                default:
                    throw new Exception("Number, identifier, string, or property expected.");
            }
            lex.NextToken();
            return ret;
        }
        /// <summary>
        /// Processes a block.  Starts at an open brace.  Eats the closing brace.
        /// </summary>
        /// <param name="lex">The lexer to read from.</param>
        /// <returns>The first syntax node in the code block.</returns>
        private static SyntaxNode ProcessCodeBlock(Lexer lex)
        {
            SyntaxNode startNode = null;
            SyntaxNode previousNode = null;
            SyntaxNode currentNode = null;

            if(lex.CurrentToken.Type != TokenType.OPEN_BRACE)
                throw new Exception("'{' expected.");
            // consume the open brace token
            lex.NextToken();
            do
            {
                switch (lex.CurrentToken.Type)
                {
                    case TokenType.KEYWORD:
                        switch (lex.CurrentToken.StringValue)
                        {
                            case "set":
                            case "event":
                                currentNode = Script.ProcessCodeLine(lex);
                                break;
                            case "if":
                                currentNode = Script.ProcessIfLine(lex);
                                break;
                        }
                        break;
                    default:
                        throw new Exception("Keyword expected.");
                }
                if (startNode == null)
                {
                    startNode = currentNode;
                    previousNode = startNode;
                }
                else
                {
                    previousNode.NextNode = currentNode;
                    previousNode = currentNode;
                }
            } while (lex.CurrentToken.Type != TokenType.CLOSE_BRACE);
            // consumes the close braces token
            lex.NextToken();
            return startNode;
        }

        public bool HasSubscript(String p)
        {
            return this.subscripts.ContainsKey(p);
        }
    }
}
